package drds.plus.sql_process.type;

import drds.tools.ShouldNeverHappenException;

import java.math.BigDecimal;
import java.math.BigInteger;


public class Encoder {
    public static int encodeByte(byte value, byte[] bytes, int offset) {
        bytes[offset] = (byte) (value ^ 0x80);
        return 1;
    }

    public static int encodeShort(Short value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        } else {
            bytes[offset] = Constants.NOT_NULL_BYTE_HIGH;
            encodeShort(value.shortValue(), bytes, offset + 1);
            return 3;
        }
    }

    public static void encodeShort(short value, byte[] bytes, int offset) {
        value ^= 0x8000;
        bytes[offset] = (byte) (value >> 8);
        bytes[offset + 1] = (byte) value;
    }


    public static int encode(Integer value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        } else {
            bytes[offset] = Constants.NOT_NULL_BYTE_HIGH;
            encode(value.intValue(), bytes, offset + 1);
            return 5;
        }
    }


    public static void encode(int value, byte[] bytes, int offset) {
        value ^= 0x80000000;
        bytes[offset] = (byte) (value >> 24);
        bytes[offset + 1] = (byte) (value >> 16);
        bytes[offset + 2] = (byte) (value >> 8);
        bytes[offset + 3] = (byte) value;
    }


    public static int encode(Long value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        } else {
            bytes[offset] = Constants.NOT_NULL_BYTE_HIGH;
            encode(value.longValue(), bytes, offset + 1);
            return 9;
        }
    }


    public static void encode(long value, byte[] bytes, int offset) {
        int $ = ((int) (value >> 32)) ^ 0x80000000;
        bytes[offset] = (byte) ($ >> 24);
        bytes[offset + 1] = (byte) ($ >> 16);
        bytes[offset + 2] = (byte) ($ >> 8);
        bytes[offset + 3] = (byte) $;
        $ = (int) value;
        bytes[offset + 4] = (byte) ($ >> 24);
        bytes[offset + 5] = (byte) ($ >> 16);
        bytes[offset + 6] = (byte) ($ >> 8);
        bytes[offset + 7] = (byte) $;
    }

    public static int encode(Float value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        } else {
            encode(value.floatValue(), bytes, offset);
            return 5;
        }
    }


    public static void encode(float value, byte[] bytes, int offset) {
        int bits = Float.floatToIntBits(value);
        bytes[offset] = (byte) (bits >> 24);
        bytes[offset + 1] = (byte) (bits >> 16);
        bytes[offset + 2] = (byte) (bits >> 8);
        bytes[offset + 3] = (byte) bits;
    }


    public static int encode(Double value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        } else {
            encode(value.doubleValue(), bytes, offset++);
            return 9;
        }
    }


    public static void encode(double value, byte[] bytes, int offset) {
        long bits = Double.doubleToLongBits(value);
        int w = (int) (bits >> 32);
        bytes[offset] = (byte) (w >> 24);
        bytes[offset + 1] = (byte) (w >> 16);
        bytes[offset + 2] = (byte) (w >> 8);
        bytes[offset + 3] = (byte) w;
        w = (int) bits;
        bytes[offset + 4] = (byte) (w >> 24);
        bytes[offset + 5] = (byte) (w >> 16);
        bytes[offset + 6] = (byte) (w >> 8);
        bytes[offset + 7] = (byte) w;
    }

    public static int calculateEncodedLength(String value) {
        if (value == null) {
            return 1;
        }
        String plainString = value;
        byte[] plainStringBytes = plainString.getBytes();
        int length = 1 + getLength(plainStringBytes.length) + plainStringBytes.length;
        return length;

    }


    private static int getLength(byte[] bytes, int offset, int b) {
        if (b == 1) {
            return bytes[offset++];
        } else if (b == 2) {

        } else if (b == 3) {

        } else if (b == 4) {

        } else if (b == 4) {

        } else if (b == 5) {

        } else {
            throw new ShouldNeverHappenException();
        }
        return 0;
    }

    private static int getLength(int value) {
        if (value < 128) {//2^7
            return 1;
        } else if (value < 16384) {//128*128
            return 2;
        } else if (value < 2097152) {//128*128*128
            return 3;
        } else if (value < 268435456) {//128*128*128*128
            return 4;
        } else {
            return 5;
        }
    }

    public static int encodeBigDecimal(BigDecimal value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        }
        String plainString = value.toPlainString();
        byte[] plainStringBytes = plainString.getBytes();
        int length = encodeLength(plainStringBytes.length, bytes, offset);
        System.arraycopy(plainStringBytes, 0, bytes, offset + 1 + length, plainStringBytes.length);
        return offset + 1 + length + plainStringBytes.length;
    }

    public static int encodeBigInteger(BigInteger value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        }
        String plainString = value.toString();
        byte[] plainStringBytes = plainString.getBytes();
        int length = encodeLength(plainStringBytes.length, bytes, offset);
        System.arraycopy(plainStringBytes, 0, bytes, offset + 1 + length, plainStringBytes.length);
        return offset + 1 + length + plainStringBytes.length;
    }

    public static int encodeString(String value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        }
        String plainString = value;
        byte[] plainStringBytes = plainString.getBytes();
        int length = encodeLength(plainStringBytes.length, bytes, offset);
        System.arraycopy(plainStringBytes, 0, bytes, offset + 1 + length, plainStringBytes.length);
        return offset + 1 + length + plainStringBytes.length;
    }


    private static int encodeLength(int value, byte[] bytes, int offset) {
        System.out.println("value:" + value);
        if (value < 128) {//2^7
            bytes[offset++] = (byte) 1;
            bytes[offset] = (byte) value;
            return 1;
        } else if (value < 16384) {//128*128
            bytes[offset++] = (byte) 2;
            bytes[offset++] = (byte) ((value >> 8) | 0x80);
            bytes[offset] = (byte) value;
            return 2;
        } else if (value < 2097152) {//128*128*128
            bytes[offset++] = (byte) 3;
            bytes[offset++] = (byte) ((value >> 16) | 0xc0);
            bytes[offset++] = (byte) (value >> 8);
            bytes[offset] = (byte) value;
            return 3;
        } else if (value < 268435456) {//128*128*128*128
            bytes[offset++] = (byte) 4;
            bytes[offset++] = (byte) ((value >> 24) | 0xe0);
            bytes[offset++] = (byte) (value >> 16);
            bytes[offset++] = (byte) (value >> 8);
            bytes[offset] = (byte) value;
            return 4;
        } else {
            bytes[offset++] = (byte) 5;
            bytes[offset++] = (byte) 0xf0;
            bytes[offset++] = (byte) (value >> 24);
            bytes[offset++] = (byte) (value >> 16);
            bytes[offset++] = (byte) (value >> 8);
            bytes[offset] = (byte) value;
            return 5;
        }
    }

    ////////////////////

    /**
     * Encodes the given character into exactly 2 bytes.
     *
     * @param value  character value to encodeString
     * @param bytes  destination for encoded bytes
     * @param offset offset into destination array
     */
    public static void encode(char value, byte[] bytes, int offset) {
        bytes[offset] = (byte) (value >> 8);
        bytes[offset + 1] = (byte) value;
    }

    /**
     * Encodes the given Character object into exactly 1 or 3 bytes. If the
     * Character object is never expected to be null, consider encoding setAliasAndSetNeedBuild a char
     * primitive.
     *
     * @param value  optional Character value to encodeString
     * @param bytes  destination for encoded bytes
     * @param offset offset into destination array
     * @return amount of bytes written
     */
    public static int encode(Character value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        } else {
            bytes[offset] = Constants.NOT_NULL_BYTE_HIGH;
            encode(value.charValue(), bytes, offset + 1);
            return 3;
        }
    }

    /**
     * Encodes the given boolean into exactly 1 byte.
     *
     * @param value  boolean value to encodeString
     * @param bytes  destination for encoded bytes
     * @param offset offset into destination array
     */
    public static void encode(boolean value, byte[] bytes, int offset) {
        bytes[offset] = value ? (byte) 128 : (byte) 127;
    }

    /**
     * Encodes the given Boolean object into exactly 1 byte.
     *
     * @param value  optional Boolean value to encodeString
     * @param bytes  destination for encoded bytes
     * @param offset offset into destination array
     */
    public static void encode(Boolean value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
        } else {
            bytes[offset] = value.booleanValue() ? (byte) 128 : (byte) 127;
        }
    }


    /**
     * Encodes the given optional byte array into a variable amount of bytes. If the
     * byte array is null, exactly 1 byte is written. Otherwise, the amount written
     * can be determined by calling calculateEncodedLength.
     *
     * @param value  byte array value to encodeString, may be null
     * @param bytes  destination for encoded bytes
     * @param offset offset into destination array
     * @return amount of bytes written
     */
    public static int encode(byte[] value, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        }
        return encode(value, 0, value.length, bytes, offset);
    }

    /**
     * Encodes the given optional byte array into a variable amount of bytes. If the
     * byte array is null, exactly 1 byte is written. Otherwise, the amount written
     * can be determined by calling calculateEncodedLength.
     *
     * @param value       byte array value to encodeString, may be null
     * @param valueOffset offset into byte array
     * @param valueLength length of data in byte array
     * @param bytes       destination for encoded bytes
     * @param offset      offset into destination array
     * @return amount of bytes written
     */
    public static int encode(byte[] value, int valueOffset, int valueLength, byte[] bytes, int offset) {
        if (value == null) {
            bytes[offset] = Constants.NULL_BYTE_HIGH;
            return 1;
        }

        // Write the value length first, in a variable amount of bytes.
        int amt = encodeUnsignedVarInt(valueLength, bytes, offset);

        // Now write the value.
        System.arraycopy(value, valueOffset, bytes, offset + amt, valueLength);

        return amt + valueLength;
    }

    /**
     * Returns the amount of bytes required to encodeString the given byte array.
     *
     * @param value byte array value to encodeString, may be null
     * @return amount of bytes needed to encodeString
     */
    public static int calculateEncodedLength(byte[] value) {
        return value == null ? 1 : calculateEncodedLength(value, 0, value.length);
    }

    /**
     * Returns the amount of bytes required to encodeString the given byte array.
     *
     * @param value       byte array value to encodeString, may be null
     * @param valueOffset offset into byte array
     * @param valueLength length of data in byte array
     * @return amount of bytes needed to encodeString
     */
    public static int calculateEncodedLength(byte[] value, int valueOffset, int valueLength) {
        return value == null ? 1 : (unsignedVarIntLength(valueLength) + valueLength);
    }


    private static int encodeUnsignedVarInt(int value, byte[] bytes, int offset) {
        if (value < 128) {
            bytes[offset] = (byte) value;
            return 1;
        } else if (value < 16384) {
            bytes[offset++] = (byte) ((value >> 8) | 0x80);
            bytes[offset] = (byte) value;
            return 2;
        } else if (value < 2097152) {
            bytes[offset++] = (byte) ((value >> 16) | 0xc0);
            bytes[offset++] = (byte) (value >> 8);
            bytes[offset] = (byte) value;
            return 3;
        } else if (value < 268435456) {
            bytes[offset++] = (byte) ((value >> 24) | 0xe0);
            bytes[offset++] = (byte) (value >> 16);
            bytes[offset++] = (byte) (value >> 8);
            bytes[offset] = (byte) value;
            return 4;
        } else {
            bytes[offset++] = (byte) 0xf0;
            bytes[offset++] = (byte) (value >> 24);
            bytes[offset++] = (byte) (value >> 16);
            bytes[offset++] = (byte) (value >> 8);
            bytes[offset] = (byte) value;
            return 5;
        }
    }

    private static int unsignedVarIntLength(int value) {
        if (value < 128) {
            return 1;
        } else if (value < 16384) {
            return 2;
        } else if (value < 2097152) {
            return 3;
        } else if (value < 268435456) {
            return 4;
        } else {
            return 5;
        }
    }


}
